


var EXPORTED_SYMBOLS = ["Si"];


let Si = (function() {
    "use strict";

    const Cu = Components.utils;
    const Ci = Components.interfaces;
    const Cc = Components.classes;
    function require(url) {
        let scope = {};
        Cu.import(url, scope);
        return scope;
    }
    let {MiscUtils} = require("chrome://jsm/content/utils.js");
    let {Prefs} = require("chrome://jsm/content/Preferences.js");
    let {FIO} = require("chrome://jsm/content/FileHelpers.js");
    let {Services} = require("resource://gre/modules/Services.jsm");
    let {OS} = require("resource://gre/modules/osfile.jsm");

    let log = function() {
        MiscUtils.browserWindow.console.log.apply(
            MiscUtils.browserWindow.console, arguments
        );
    }

    function ScriptingInterface() {
        this.clients = new Map();
        this.created = this.getLibPath().then( libpath => {
            this.worker = new ChromeWorker(
                "chrome://imacros/content/si_main.js"
            );
            this.worker.onerror = Cu.reportError.bind(Cu);
            this.worker.onmessage = this.onMessage.bind(this);
            this.worker.postMessage({
                "command":  "init",
                "libpath":  libpath
            });
            return;
        }); 
    }


    ScriptingInterface.prototype.getLibPath = function() {
        let path = "";
        if (MiscUtils.isWindows()) {
            
            let wrk = Cc["@mozilla.org/windows-registry-key;1"]
                .createInstance(Ci.nsIWindowsRegKey);
            try {
                wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE,
                         "SOFTWARE\\iOpus\\iMacros",
                         wrk.ACCESS_READ);
            } catch(e) {
                
                return Promise.reject(
                    new Error("iMacros: can not find iimFirefoxConnector!")
                );
            }
            path = wrk.readStringValue("PathExe");
            wrk.close();
            path = OS.Path.join(path, "iimFirefoxConnector.dll");
        } else {
            let is_64bit = /^x86_64/i.test(Services.appinfo.XPCOMABI);
            
            
            path = OS.Path.join(
                OS.Constants.Path.homeDir,
                "iMacros",
                (is_64bit ? "iimFirefoxConnector64.so" : 
                 "iimFirefoxConnector.so")
            );
        }

        return OS.File.exists(path).then(exists => {
            if (exists)
                return path;
            else
		throw new Error("iMacros: can not find iimFirefoxConnector!");
        });
    };
        

    ScriptingInterface.prototype.sendResponse = function(
        clientId, message, errorCode, extra
    ) {
        
        if (errorCode < 0 && !(/error\: /i.test(message))) {
            message = "Error: "+message;
        }
        message += " ("+errorCode+")";
        let result = {
            status: message,
            errorCode: errorCode,
            extractData: (extra && extra.extractData) ?
                extra.extractData.split("[EXTRACT]") : "",
            imageData: (extra && extra.imageData) ? extra.imageData : {},
            lastPerformance: extra ? extra.lastPerformance : [],
            waitForProcessId: extra ? extra.waitForProcessId : 0,
            profilerData: extra && extra.profilerData ? extra.profilerData : ""
        };

        let s = JSON.stringify(result);
        s = s.replace(/\\u000a/g, "\\n");
        let msg = {"command": "send_response", 
                   "clientId": clientId, "response": s};
        this.worker.postMessage(msg);
    };
     
    ScriptingInterface.prototype.openBrowserWindow = function (uri) {
        let arg = Cc["@mozilla.org/supports-string;1"]
            .getService(Ci.nsISupportsString);
        arg.data = uri;

        
        let chromeURL = Services.prefs.getCharPref("browser.chromeURL");
        return Services.ww.openWindow(null, chromeURL,
                                      "_blank", "chrome,dialog=no,all", arg);
    };

    ScriptingInterface.prototype.saveClient = function (clientId, win) {
        this.clients.set(clientId, {winId: win.iMacros.wid, in_use: true});
    };

    ScriptingInterface.prototype.removeClient = function(clientId) {
        this.clients.delete(clientId);
    };


    ScriptingInterface.prototype.releaseClient = function(clientId) {
        if (this.clients.has(clientId)) {
            let client = this.clients.get(clientId);
            client.in_use = false;
        }
    };

    
    ScriptingInterface.prototype.getWindow = function(win_id) {
        let windows = Services.wm.getEnumerator("navigator:browser");
        while(windows.hasMoreElements()) {
            let win = windows.getNext().QueryInterface(Ci.nsIDOMWindow);
            if (win.iMacros && win.iMacros.wid == win_id)
                return win;
        }
    };


    ScriptingInterface.prototype.whenReady = function(win) {
        return new Promise((resolve, reject) => {
            if (win.document && win.document.readyState == "complete") {
                resolve(win);
            } else {
                win.addEventListener("load", function l () {
                    win.removeEventListener("load", l);
                    resolve(win);
                });
            }
        });
    };

    ScriptingInterface.prototype.onInit = function(clientId, args) {
        let win_p = null;
        if (args.launched) { 
            let win = Services.wm.getMostRecentWindow("navigator:browser");
            win_p = this.whenReady(win);
        } else {
            if (args.openNewBrowser) {
                win_p = this.whenReady(this.openBrowserWindow("about:blank"));
            } else {
                let windows = Services.wm.getEnumerator("navigator:browser");
                let promises = [];
                
                
                while(windows.hasMoreElements()) {
                    let win = windows.getNext().QueryInterface(Ci.nsIDOMWindow);
                    promises.push(win);
                }

                win_p = Promise.all(promises).then(windows => {
                    for (let win of windows) {
                        let found = false;
                        for (let client of this.clients.values()) {
                            if (client.winId == win.iMacros.wid)
                                if (client.in_use) {
                                    found = true;
                                    break;
                                } else {
                                    client.in_use = true;
                                    return this.whenReady(win);
                                }
                        }
                        if (!found)
                            return this.whenReady(win);
                    }
                    
                    let win = this.openBrowserWindow("about:blank");
                    return this.whenReady(win);
                });
            }
        }

        win_p.then(win => {
            this.sendResponse(clientId, "OK", 1);
            this.saveClient(clientId, win);
        });
    };


    ScriptingInterface.prototype.onPlay = function(clientId, args) {
        if (!this.clients.has(clientId))
            throw new Error("Client id "+clientId+" does not exist");
        var play_args = {
            clientId: clientId,
            vars: args.vars,
            use_profiler: args.use_profiler
        };
        let win = this.getWindow(this.clients.get(clientId).winId);
        if (/^CODE:((?:\n|.)+)$/.test(args.source)) { 
            let val = RegExp.$1;
            val = val.replace(/\[sp\]/ig, ' ');
            val = val.replace(/\[br\]/ig, '\n');
            val = val.replace(/\[lf\]/ig, '\r');
            play_args.type = "source";
            play_args.source = val;
            Services.obs.notifyObservers(win, "imacros-si-play", 
                                         JSON.stringify(play_args));
        } else {  
            play_args.type = "file";
            let name = args.source;
            if (!/\.(?:js|iim)$/i.test(name))
                name += ".iim";
            let file_path = FIO.isFullPath(name) ? name : 
                FIO.getMacroPath(name);
            OS.File.exists(file_path).then(exists => {
                if (!exists) {
                    this.sendResponse(clientId, "Can not open macro "+ name, 
                                      -931);
                    return;
                } else {
                    play_args.filePath = file_path;
                    Services.obs.notifyObservers(win, "imacros-si-play", 
                                     JSON.stringify(play_args));
                }
            });
        }
    },


    ScriptingInterface.prototype.onExit = function(clientId, args) {
        let counter = 0;
        let extra = {waitForProcessId: 0};
        let windows = Services.wm.getEnumerator("navigator:browser");
        while(windows.hasMoreElements()) {
            var dummy = windows.getNext();
            counter++;
        }

        if (counter == 1) {
            
            
            extra.waitForProcessId = this.pid;
        }

        this.sendResponse(clientId, "OK", 1, extra);
        if (extra.waitForProcessId) {
            this.worker.postMessage({"command": "terminate"});
        }
        let win = this.getWindow(this.clients.get(clientId).winId);
        if (win) {
            Promise.resolve().then(() => win.BrowserTryToCloseWindow());
        }
        this.removeClient(clientId);
    };
    

    ScriptingInterface.prototype.onMessage = function(evt) {
        let msg = evt.data;
        
        switch (msg.type) {
        case "request":
            this.onRequest(msg.request, msg.clientId);
            break;
        case "error":
            Cu.reportError(msg.message);
            break;
        case "message": 
            Cu.reportError(msg.message);
            break;
        case "pid":
            this.pid = msg.pid;
            break;
        }
    };
            

    ScriptingInterface.prototype.onRequest = function(req, clientId) {
        try {
            var req = JSON.parse(req);
        } catch(e) {
            Cu.reportError(e);
            
            this.sendResponse(
                clientId, "Can not parse request \""+command+"\"", -1
            );
            return;
        }

        
        try {
            switch(req.type) {
            case "init":
                this.onInit(clientId, req.args);
                break;

            case "play":
                this.onPlay(clientId, req.args);
                break;

            case "exit":
                this.onExit(clientId, req.args);
                break;

            case "show":
                if (!this.clients.has(clientId))
                    throw new Error("no client with Id "+clientId);
                var win = this.getWindow(this.clients.get(clientId).winId);
                var show_args = {
                    clientId: clientId,
                    message: req.args.message
                };
                Services.obs.notifyObservers(win, "imacros-si-show", 
                                             JSON.stringify(show_args));
                break;

            case "capture":
                if (!this.clients.has(clientId))
                    throw new Error("no client with Id "+clientId);
                var win = this.getWindow(this.clients.get(clientId).winId);
                let file_path = FIO.isFullPath(req.args.path) ? req.args.path :
                    OS.Path.join(Prefs.defaultDownloadPath, 
                                 ...OS.Path.split(req.args.path).components);
                let capture_args = {
                    clientId: clientId,
                    filePath: file_path,
                    type: req.args.type
                };
                Services.obs.notifyObservers(
                    win, "imacros-si-capture", JSON.stringify(capture_args)
                );
                break;

            default:
                this.sendResponse(clientId, "Unknown command "+req.type, -1000);
                break;
            }
        } catch(e) {
            Cu.reportError(e);
            this.sendResponse(clientId, e.toString(), -1000);
        }
    };


    ScriptingInterface.prototype.initWithPipe = function(pipe) {
        return this.created.then(() => {
            if (this.m_pipe == pipe)
                return;             
            this.m_pipe = pipe;
            if (this.m_port)
                delete this.m_port;
            this.worker.postMessage(
                {"command": "start", "pipe": pipe}
            );
        }).catch(Cu.reportError);
    };


    return new ScriptingInterface();

}) ();


